use rhai::exported_module;
use std::sync::Arc;
use std::sync::Mutex;

use rhai::Engine;
use rhai::Scope;
use rhai::AST;

use crate::model::indicators::macd::MACD;
use crate::model::indicators::rsi::RSI;
use crate::model::indicators::IndicatorActionWrap;
use crate::model::kline::Kline;
use anyhow::Result;

use super::trade_api::trade_api;
use super::trade_api::TradeContext;

/// TradeEngine, encapsulates the logic for trading.
pub struct TradeEngine {
    /// rhai engine
    engine: Engine,
    /// rhai scope
    pub scope: Scope<'static>, // Use 'static lifetime
    /// klines window size
    kl_window: u8,
}

impl TradeEngine {
    pub fn get_trade_bot_id(&self) -> String {
        // 尝试解锁共享的 TradeContext
        let tapi_mutex = self.scope
            .get_value::<Arc<Mutex<TradeContext>>>("Tapi")
            .expect("Tapi should exist");
        let context = tapi_mutex.lock().expect("Tapi mutex lock failed");

        context.trade_bot_id.clone()
    }
    /// add new kline to the window_klines
    pub fn next_kline(&mut self, new_kline: &Kline) {
        // 尝试解锁共享的 TradeContext
        let tapi_mutex = self.scope
            .get_value::<Arc<Mutex<TradeContext>>>("Tapi")
            .expect("Tapi should exist");
        let mut context = tapi_mutex.lock().expect("Tapi mutex lock failed");

        // 如果窗口已满，移除最老的 K线数据
        if context.window_klines.len() >= usize::from(self.kl_window) {
            context.window_klines.remove(0);
        }
        // 将新 K线数据添加到窗口的末尾
        context.window_klines.push(new_kline.clone());
    }
    pub fn new(trade_bot_id: &str, kl_window: u8) -> Self {
        let mut engine = Engine::new();

        build_types(&mut engine);

        // Register the trading plugin module with the Rhai engine
        engine.register_global_module(exported_module!(trade_api).into());

        // Initialize a new scope with 'static lifetime
        let mut scope: Scope<'static> = Scope::new();
        let shared_trade_context = Arc::new(
            Mutex::new(TradeContext::new(trade_bot_id.into(), Vec::new()))
        );
        scope.push_constant("Tapi", shared_trade_context);

        TradeEngine {
            engine,
            scope,
            kl_window,
        }
    }

    /// Verify script,
    ///
    /// # Arguments script: &str , 脚本内容
    ///
    /// # Returns: Result<AST>
    ///
    pub fn verify_script(&mut self, script: &str) -> Result<AST> {
        // https://rhai.rs/book/engine/scope.html
        // Save the current size of the 'scope'
        let orig_scope_size = self.scope.len();
        // Example data for the scope, in practice you might want to populate this dynamically
        // let kline_example = Kline::new(100.0, 120.0, 90.0, 110.0, 1000.0, 1628800000);
        // scope.push("example_kline", kline_example);

        let ast = self.engine.compile_scripts_with_scope(&self.scope, [script])?;

        // Rewind the 'scope' to the original size
        self.scope.rewind(orig_scope_size);

        Ok(ast)
    }

    /// Run a script，
    ///
    /// # Arguments： script: script content
    ///
    /// # Returns:
    ///
    pub fn run_script(&mut self, script: &str) -> Result<()> {
        // https://rhai.rs/book/engine/scope.html
        // Save the current size of the 'scope'
        let orig_scope_size = self.scope.len();
        // Example data for the scope, in practice you might want to populate this dynamically
        // let kline_example = Kline::new(100.0, 120.0, 90.0, 110.0, 1000.0, 1628800000);
        // scope.push("example_kline", kline_example);

        let ast = self.engine.compile_scripts_with_scope(&self.scope, [script])?;

        // Run the compiled AST
        let rs = self.engine.run_ast_with_scope(&mut self.scope, &ast);
        println!("(trade_engine::run_script) rs={:?}", rs);

        // // Now you can lock and use `shared_trade_context`
        // let context = shared_trade_context.lock().unwrap();
        // println!(
        //     "(trade_engine::run_script) trade_request_queue--1: {:?}",
        //     context.trade_request_queue
        // );

        // Rewind the 'scope' to the original size
        self.scope.rewind(orig_scope_size);

        // // Now you can lock and use `shared_trade_context`
        // let context = shared_trade_context.lock().unwrap();
        // println!(
        //     "(trade_engine::run_script) trade_request_queue--2: {:?}",
        //     context.trade_request_queue
        // );
        Ok(())
    }
}

fn build_types(engine: &mut Engine) {
    // Build the custom type
    engine.build_type::<IndicatorActionWrap>();
    engine.build_type::<Kline>();
    engine.build_type::<MACD>();
    engine.build_type::<RSI>();
}

#[cfg(test)]
mod tests {
    use std::fs;

    use chrono::Utc;
    use rand::Rng;
    use surrealdb::sql::Id;
    use surrealdb::sql::Thing;

    use crate::backtest::trade_api::SharedTradeContext;
    use crate::backtest::trade_engine::TradeEngine;
    use crate::db;
    use crate::model::indicators::macd::MACD;
    use crate::model::indicators::rsi::RSI;
    use crate::model::indicators::IndicatorActionWrap;
    use crate::model::kline::Kline;
    use crate::model::kline::Klt;

    const SCRIPT_PATH_ROOT: &str =
        "/Users/CC/gitee/conanchen/fuquants-workspace/crates/fudata/src/backtest/";

    #[tokio::test]
    async fn test_verify_script() {
        let mut trade_engine = TradeEngine::new("trade_bot_id001", 3);
        // Run scripts...
        let scripts = vec![
            // "kline.rhai",
            // "trade_api.rhai",
            // "indicators/macd.rhai",
            "indicators/rsi.rhai"
        ];
        for script in scripts {
            let script_path = &format!("{}{}", SCRIPT_PATH_ROOT, script);
            // Load the script content from the file
            let script_content = fs
                ::read_to_string(script_path)
                .expect("Failed to read script file");
            // Now use the loaded script content to call the verify function
            let rs = trade_engine.verify_script(&script_content);
            assert!(rs.is_ok());

            println!("Verified OK [{}]", script);
        }
    }

    #[tokio::test]
    async fn test_run_file() {
        let _ = db::init().await.unwrap();

        let mut trade_engine: TradeEngine = TradeEngine::new("trade_bot_id001", 3);

        // Run scripts...
        let scripts = vec![
            // "kline.rhai",
            // "trade_api.rhai",
            "indicators/macd.rhai"
            // "indicators/rsi.rhai",
        ];
        let klines = dummy_fetch_klines(10, Klt::DAY);
        for script in scripts {
            let script_path = &format!("{}{}", SCRIPT_PATH_ROOT, script);

            for (idx, kline) in klines.iter().enumerate() {
                trade_engine.next_kline(kline);

                // Load the script content from the file
                let script_content = fs
                    ::read_to_string(script_path)
                    .expect("Failed to read script file");
                // Now use the loaded script content to call the verify function
                let rs = trade_engine.run_script(&script_content);

                println!("\n[{idx}]~~~~~~~~~~~~~~~~\n");
                assert!(rs.is_ok());
            }

            // after running all klines on the script, trade_engine.scope.trade_context should hold the trade_requests
            let shared_trade_context: Option<SharedTradeContext> =
                trade_engine.scope.get_value("Tapi");
            if let Some(shared_trade_context) = shared_trade_context {
                let trade_context = shared_trade_context.lock().unwrap();

                trade_context.trade_request_queue
                    .lock()
                    .unwrap()
                    .iter()
                    .enumerate()
                    .for_each(|(index, trade_request)| {
                        println!("trade_request[{index}]= {:?}", trade_request);
                    });
            }

            println!("Run OK for script: [{}]", script);
        }
    }

    // Generate dummy Klines with associated indicators
    fn dummy_fetch_klines(num_days: u8, klt: Klt) -> Vec<Kline> {
        let mut rng = rand::thread_rng();
        let mut klines = Vec::with_capacity(num_days as usize);

        for _ in 0..num_days {
            let kline = Kline::dummy_generate(klt, &mut rng);
            klines.push(kline);
        }

        klines
    }

    impl MACD {
        fn dummy_generate(&mut self, prev_macd: f64, prev_signal: f64, rng: &mut impl Rng) {
            let random_macd_change = rng.gen_range(-1.0..1.0);
            let random_signal_change = rng.gen_range(-0.5..0.5);

            let macd = prev_macd + random_macd_change;
            let signal = prev_signal + random_signal_change;
            let bar = macd - signal;

            let (signal0, signal1) = if macd > signal && prev_macd <= prev_signal {
                // Crossed signal line upwards
                (
                    IndicatorActionWrap {
                        buy: rng.gen_range(1..=100) as i64,
                        sell: 0,
                    },
                    IndicatorActionWrap { buy: 0, sell: 0 },
                )
            } else if macd < signal && prev_macd >= prev_signal {
                // Crossed signal line downwards
                (
                    IndicatorActionWrap {
                        sell: rng.gen_range(1..=100) as i64,
                        buy: 0,
                    },
                    IndicatorActionWrap { buy: 0, sell: 0 },
                )
            } else {
                (IndicatorActionWrap { buy: 0, sell: 0 }, IndicatorActionWrap { buy: 0, sell: 0 })
            };

            let signal1_adjust = if macd > 0.0 && prev_macd <= 0.0 {
                // Crossed zero line upwards
                IndicatorActionWrap {
                    buy: rng.gen_range(1..=100) as i64,
                    sell: 0,
                }
            } else if macd < 0.0 && prev_macd >= 0.0 {
                // Crossed zero line downwards
                IndicatorActionWrap {
                    sell: rng.gen_range(1..=100) as i64,
                    buy: 0,
                }
            } else {
                IndicatorActionWrap { buy: 0, sell: 0 }
            };

            self.signal0 = Some(signal0);
            self.signal1 = Some(signal1_adjust);
            self.macd = macd;
            self.signal = signal;
            self.bar = bar;
        }
    }
    impl RSI {
        fn dummy_generate(&mut self, prev_main: f64, rng: &mut impl Rng) {
            const OVERBOUGHT: f64 = 0.7;
            const OVERSOLD: f64 = 0.3;

            let random_rsi_change = rng.gen_range(-0.1..0.1);

            let main = (prev_main + random_rsi_change).clamp(0.0, 1.0);
            let (signal0, signal1) = if main > OVERBOUGHT && prev_main <= OVERBOUGHT {
                // Entered overbought
                (
                    IndicatorActionWrap {
                        sell: rng.gen_range(1..=100) as i64,
                        buy: 0,
                    },
                    IndicatorActionWrap { buy: 0, sell: 0 },
                )
            } else if main < OVERSOLD && prev_main >= OVERSOLD {
                // Entered oversold
                (
                    IndicatorActionWrap {
                        buy: rng.gen_range(1..=100) as i64,
                        sell: 0,
                    },
                    IndicatorActionWrap { buy: 0, sell: 0 },
                )
            } else {
                (IndicatorActionWrap { buy: 0, sell: 0 }, IndicatorActionWrap { buy: 0, sell: 0 })
            };

            let signal1_adjust = if main < OVERBOUGHT && prev_main >= OVERBOUGHT {
                // Left overbought
                IndicatorActionWrap {
                    sell: rng.gen_range(1..=100) as i64,
                    buy: 0,
                }
            } else if main > OVERSOLD && prev_main <= OVERSOLD {
                // Left oversold
                IndicatorActionWrap {
                    buy: rng.gen_range(1..=100) as i64,
                    sell: 0,
                }
            } else {
                IndicatorActionWrap { buy: 0, sell: 0 }
            };

            self.signal0 = Some(signal0);
            self.signal1 = Some(signal1_adjust);
            self.upper = OVERBOUGHT;
            self.main = main;
            self.lower = OVERSOLD;
        }
    }
    impl Kline {
        fn dummy_generate(klt: Klt, rng: &mut impl Rng) -> Self {
            let timestamp = Utc::now(); // or use specific logic to set klts
            let open = rng.gen_range(100.0..200.0);
            let high = rng.gen_range(open..250.0);
            let low = rng.gen_range(50.0..=open); // Ensure low is not greater than open
            let close = loop {
                let temp_close = rng.gen_range(low..=high);
                if low <= temp_close && temp_close <= high {
                    break temp_close;
                }
            };
            let volume = rng.gen_range(1000u64..100000u64);

            let mut macd = MACD {
                signal0: None,
                signal1: None,
                macd: 0.0,
                signal: 0.0,
                bar: 0.0,
            };
            macd.dummy_generate(0.0, 0.0, rng);

            let mut rsi = RSI {
                signal0: None,
                signal1: None,
                upper: 0.7,
                main: 0.5, // Starting value
                lower: 0.3,
            };
            rsi.dummy_generate(0.5, rng);

            // Placeholder for BOLL generation, assuming similar logic to MACD and RSI
            // let boll = None; // Implement if needed

            Kline {
                id: Some(Thing::from(("kline", Id::ulid()))), // Implement Thing creation logic if required
                klt,
                klts: timestamp,
                open,
                high,
                low,
                close,
                volume,
                // boll,
                // rsi: Some(rsi),
                // macd: Some(macd),
                ..Default::default()
            }
        }
    }
}
